//
// This code was created by Jeff Molofee '99 (ported to Linux/GLUT by Richard Campbell '99)
//
// If you've found this code useful, please let me know.
//
// Visit me at www.demonews.com/hosted/nehe
// (email Richard Campbell at ulmont@bellsouth.net)
//
#include <GL/glut.h>    // Header File For The GLUT Library
#include <GL/gl.h>	// Header File For The OpenGL32 Library
#include <GL/glu.h>	// Header File For The GLu32 Library
#include <unistd.h>     // Header file for sleeping.

#include <cstdio>
#include <iostream>

/* ascii code for the escape key */
#define ESCAPE 27

using namespace std;

/* The number of our GLUT window */
int window;
bool keys[256];
bool fullscreen = true;

GLfloat     xrot;                           // X Rotation
GLfloat     yrot;                           // Y Rotation
GLfloat     zrot;                           // Z Rotation

GLuint      texture[6];                     // Storage For One Texture

/* Image type - contains height, width, and data */
typedef struct {
	unsigned long sizeX;
	unsigned long sizeY;
	char *data;
} Image;

// quick and dirty bitmap loader...for 24 bit bitmaps with 1 plane only.
// See http://www.dcs.ed.ac.uk/~mxr/gfx/2d/BMP.txt for more info.
bool ImageLoad(char *filename, Image *image) {
	FILE *file;
	unsigned long size;                 // size of the image in bytes.
	unsigned long i;                    // standard counter.
	unsigned short int planes;          // number of planes in image (must be 1)
	unsigned short int bpp;             // number of bits per pixel (must be 24)
	char temp;                          // temporary color storage for bgr-rgb conversion.

	// make sure the file is there.
	if ((file = fopen(filename, "rb"))==NULL){
		cout << "File Not Found : " << filename << endl;
		return false;
	}

	// seek through the bmp header, up to the width/height:
	fseek(file, 18, SEEK_CUR);

	// read the width
	if ((i = fread(&image->sizeX, 4, 1, file)) != 1) {
		cout << "Error reading width from " << filename << endl;
		return false;
	}
	printf("Width of %s: %lu\n", filename, image->sizeX);

	// read the height
	if ((i = fread(&image->sizeY, 4, 1, file)) != 1) {
		cout << "Error reading height from %s " << filename << endl;
		return false;
	}
	cout << "Height of " << filename << ": " << image->sizeY << endl;

	// calculate the size (assuming 24 bits or 3 bytes per pixel).
	size = image->sizeX * image->sizeY * 3;

	// read the planes
	if ((fread(&planes, 2, 1, file)) != 1) {
		cout << "Error reading planes from " << filename << endl;
		return false;
	}
	if (planes != 1) {
		cout << "Planes from " << filename << " is not 1: " << planes << endl;
		return false;
	}

	// read the bpp
	if ((i = fread(&bpp, 2, 1, file)) != 1) {
		cout << "Error reading bpp from " << filename << endl;
		return false;
	}
	if (bpp != 24) {
		cout << "Bpp from " << filename << " is not 24: " << bpp << endl;
		return false;
	}

	// seek past the rest of the bitmap header.
	fseek(file, 24, SEEK_CUR);

	// read the data.
	image->data = (char *) malloc(size);
	if (image->data == NULL) {
		cout << "Error allocating memory for color-corrected image data" << endl ;
		return false;
	}

	if ((i = fread(image->data, size, 1, file)) != 1) {
		cout << "Error reading image data from " << filename << endl;
		return false;
	}

	for (i=0;i<size;i+=3) { // reverse all of the colors. (bgr -> rgb)
		temp = image->data[i];
		image->data[i] = image->data[i+2];
		image->data[i+2] = temp;
	}

	// we're done.
	return true;
}

// Load Bitmaps And Convert To Textures
bool LoadGLTextures(char *filename,int i) {
	// Load Texture
	Image *image1;

	// allocate space for texture
	image1 = (Image *) malloc(sizeof(Image));
	if (image1 == NULL) {
		cout << "Error allocating space for image" << endl;
		return false;
	}

	if (!ImageLoad(filename, image1)) {
		return false;
	}

	glGenTextures(1, &texture[i]);				// Create Texture
	glBindTexture(GL_TEXTURE_2D, texture[i]);   // 2d texture (x and y size)

	// 2d texture, level of detail 0 (normal), 3 components (red, green, blue), x size from image, y size from image,
	// border 0 (normal), rgb color data, unsigned byte data, and finally the data itself.
	glTexImage2D(GL_TEXTURE_2D, 0, 3, image1->sizeX, image1->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, image1->data);

	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); // scale linearly when image bigger than texture
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // scale linearly when image smalled than texture

	if (image1){
		if (image1->data){
			free(image1->data);
		}
		free(image1);
	}
	return true;
}

/* A general OpenGL initialization function.  Sets all of the initial parameters. */
bool InitGL(int Width, int Height)	        // We call this right after our OpenGL window is created.
{
	char filename[6][16] = {
			"Data/NeHe1.bmp",
			"Data/NeHe2.bmp",
			"Data/NeHe3.bmp",
			"Data/NeHe4.bmp",
			"Data/NeHe5.bmp",
			"Data/NeHe6.bmp",
	};
	int i;

	for (i=0; i<6; i++){
		LoadGLTextures(filename[i],i);
	}

	glEnable(GL_TEXTURE_2D);

	glShadeModel(GL_SMOOTH);				// Enables Smooth Color Shading
	glClearColor(0.0f, 0.0f, 0.0f, 0.5f);	// This Will Clear The Background Color To Black
	glClearDepth(1.0);						// Enables Clearing Of The Depth Buffer
/*
	glEnable(GL_DEPTH_TEST);				// Enables Depth Testing
	glDepthFunc(GL_EQUAL);					// The Type Of Depth Test To Do
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);	// Really Nice Perspective Calculations
*/
	glDepthFunc(GL_LESS);			// The Type Of Depth Test To Do
	glEnable(GL_DEPTH_TEST);			// Enables Depth Testing
	glShadeModel(GL_SMOOTH);			// Enables Smooth Color Shading

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();				// Reset The Projection Matrix

	gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f);	// Calculate The Aspect Ratio Of The Window

	glMatrixMode(GL_MODELVIEW);

	return true;
}

/* The function called when our window is resized (which shouldn't happen, because we're fullscreen) */
void ReSizeGLScene(int Width, int Height)
{
	if (Height==0)				// Prevent A Divide By Zero If The Window Is Too Small
		Height=1;

	glViewport(0, 0, Width, Height);		// Reset The Current Viewport And Perspective Transformation

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	// Calculate The Aspect Ratio Of The Window
	gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f);
	glMatrixMode(GL_MODELVIEW);

	glLoadIdentity();
}

/* The main drawing function. */
void DrawGLScene()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);     // Clear The Screen And The Depth Buffer
	glLoadIdentity();                       				// Reset The View

	glTranslatef(0.0f,0.0f,-5.0f);                 // Move Left 1.5 Units And Into The Screen 6.0

	glRotatef(xrot,1.0f,0.0f,0.0f);                     // Rotate On The X Axis
	glRotatef(yrot,0.0f,1.0f,0.0f);                     // Rotate On The Y Axis
	glRotatef(zrot,0.0f,0.0f,1.0f);                     // Rotate On The Z Axis

	glBindTexture(GL_TEXTURE_2D, texture[0]);           // Select Our Texture
	glBegin(GL_QUADS);
	// Front Face
	glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);  // Bottom Left Of The Texture and Quad
	glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);  // Bottom Right Of The Texture and Quad
	glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);  // Top Right Of The Texture and Quad
	glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);  // Top Left Of The Texture and Quad
	glEnd();
	// Back Face
	glBindTexture(GL_TEXTURE_2D, texture[1]);           // Select Our Texture
	glBegin(GL_QUADS);
	glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);  // Bottom Right Of The Texture and Quad
	glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);  // Top Right Of The Texture and Quad
	glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);  // Top Left Of The Texture and Quad
	glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);  // Bottom Left Of The Texture and Quad
	glEnd();
	// Top Face
	glBindTexture(GL_TEXTURE_2D, texture[2]);           // Select Our Texture
	glBegin(GL_QUADS);
	glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);  // Top Left Of The Texture and Quad
	glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.0f,  1.0f);  // Bottom Left Of The Texture and Quad
	glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.0f,  1.0f);  // Bottom Right Of The Texture and Quad
	glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);  // Top Right Of The Texture and Quad
	glEnd();
	// Bottom Face
	glBindTexture(GL_TEXTURE_2D, texture[3]);           // Select Our Texture
	glBegin(GL_QUADS);
	glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);  // Top Right Of The Texture and Quad
	glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);  // Top Left Of The Texture and Quad
	glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);  // Bottom Left Of The Texture and Quad
	glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);  // Bottom Right Of The Texture and Quad
	glEnd();
	// Right face
	glBindTexture(GL_TEXTURE_2D, texture[4]);           // Select Our Texture
	glBegin(GL_QUADS);
	glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);  // Bottom Right Of The Texture and Quad
	glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);  // Top Right Of The Texture and Quad
	glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);  // Top Left Of The Texture and Quad
	glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);  // Bottom Left Of The Texture and Quad
	glEnd();
	// Left Face
	glBindTexture(GL_TEXTURE_2D, texture[5]);           // Select Our Texture
	glBegin(GL_QUADS);
	glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);  // Bottom Left Of The Texture and Quad
	glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);  // Bottom Right Of The Texture and Quad
	glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);  // Top Right Of The Texture and Quad
	glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);  // Top Left Of The Texture and Quad
	glEnd();

	xrot+=0.6f;                             // X Axis Rotation
	yrot+=0.4f;                             // Y Axis Rotation
	zrot+=0.8f;                             // Z Axis Rotation

    // since this is double buffered, swap the buffers to display what just got drawn.
    glutSwapBuffers();
}

/* The function called whenever a key is pressed. */
void keyPressed(unsigned char key, int x, int y)
{
	/* avoid thrashing this procedure */
	usleep(100);

	/* If escape is pressed, kill everything. */
	if (key == ESCAPE)
	{
		/* shut down our window */
		glutDestroyWindow(window);

		/* exit the program...normal termination. */
		exit(0);
	}

	if (key == 98)
	{
		glutDestroyWindow(window); 		// Kill Our Current Window
		fullscreen = !fullscreen;		// Toggle Fullscreen / Windowed Mode
		// Recreate Our OpenGL Window ( Modified )
		window = glutCreateWindow("NeHe's Rotation Tutorial");
		glutDisplayFunc(&DrawGLScene);

		if (fullscreen)
			glutFullScreen();
		glutIdleFunc(&DrawGLScene);

		InitGL(640,480);
	}
}

int main(int argc, char **argv)
{
	/* Initialize GLUT state - glut will take any command line arguments that pertain to it or
     X Windows - look at its documentation at http://reality.sgi.com/mjk/spec3/spec3.html */
	glutInit(&argc, argv);

	/* Select type of Display mode:
     Double buffer
     RGBA color
     Alpha components supported
     Depth buffer */
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH);

	/* get a 640 x 480 window */
	glutInitWindowSize(640, 480);

	/* the window starts at the upper left corner of the screen */
	glutInitWindowPosition(0, 0);

	/* Open a window */
	window = glutCreateWindow("Jeff Molofee's GL Code Tutorial ... NeHe '99");

	/* Register the function to do all our OpenGL drawing. */
	glutDisplayFunc(&DrawGLScene);

	/* Go fullscreen.  This is as soon as possible. */
	//glutFullScreen();
	fullscreen = false;

	/* Even if there are no events, redraw our gl scene. */
	glutIdleFunc(&DrawGLScene);

	/* Register the function called when our window is resized. */
	glutReshapeFunc(&ReSizeGLScene);

	/* Register the function called when the keyboard is pressed. */
	glutKeyboardFunc(&keyPressed);

	/* Initialize our window. */
	InitGL(640, 480);

	/* Start Event Processing Engine */
	glutMainLoop();

	return 1;
}

